		#pragma once

#include <iostream>

#define NL std::endl

namespace cvm
{	
	void OutputConstants(::VM* vm, std::ostream& o) {
		for (int i=0; i < vm->constants.cnt; ++i) 
			o << " " << vm_constant(i) << NL;
	}

	byte* OutputInstruction(byte* b, std::ostream& o) {
		o << Op::OpCodeName(*b);
		int args = Op::OpCodeWidth(*b) - 1;
		++b;
		if (args > 0) 
		{
			o << '(';
			for (int i=0; i < args; ++i) {
				if (i > 0) o << ',';
				o << (int)*b++; 
			}
			o << ')';
		}
		o << " ";
		return b;
	}

	void OutputCode(::VM* vm, std::ostream& o) {
		int i = 0;
		for (byte* b = vm_code_begin();	b < vm_code_end(); ++b) {
			int n = b - vm_code_begin();
			if (n == 0 || b[-1] == _ret || b[-1] == _tail)
			{
				if (n > 0) o << NL;
				o << "function " << i++ << ": ";
			}
			b = OutputInstruction(b, o);
		}
		o << NL;
	}

	void OutputFunctions(::VM* vm, std::ostream& o) {
		byte** begin = vm_fun_begin();
		byte** end = vm_fun_end();		
		int i = 0;
		for (byte** cur = begin; cur != end; cur++) {
			byte* code = vm_code_begin();
			int findex = *cur - code;
			o << "function " << i++ << " " << findex << NL;	
		}
	}

	std::ostream& operator<<(std::ostream& o, VM& vm) {
		o << "stack: ";
		if (vm.Empty())  {
			o << "_Empty_" << NL; 
		}
		else  {
			for (int i=vm.Count(); i > 0; --i) 
				o << vm.At(i - 1) << " ";
			o << NL;
		}
		o << "aux stack: ";
		if (vm.AuxEmpty())  {
			o << "_Empty_" << NL; 
		}
		else  {
			for (int i=vm.AuxCount(); i > 0; --i) 
				o << vm.AuxAt(i) << " ";
			o << NL;
		}
		o << "next : ";
		OutputInstruction(vm.vm->ip, o);
		o << NL;
		return o;
	}

	void RunTest(Define& d, int expected)
	{
		VM vm;
		Assembler ar(&d); 
		ar.ApplyToVM(vm.vm); 
		std::cout << "Constants" << NL;
		OutputConstants(vm.vm, std::cout);
		std::cout << "Functions" << NL;
		OutputFunctions(vm.vm, std::cout);
		std::cout << "Code" << NL;
		OutputCode(vm.vm, std::cout);
		std::cout << "Starting test" << NL;
		std::cout << vm;
		int step = 0;
		while (true) { 
			std::cout << "Step: " << step++ << " executing ";
			OutputInstruction(vm.vm->ip, std::cout);
			std::cout << NL;
			if (vm_next(vm.vm))
				break;
			std::cout << vm;
		}
		if (vm.Empty()) {
			std::cout << "FAILED TEST: no values on stack" << NL;
		}
		else if (vm.Top()->type != IntType) {
			std::cout << "FAILED TEST: not an int on stack" << NL;
		}
		else if (val_to_int(vm.Top()) != expected) {
			std::cout << "FAILED TEST: top not equal to " << expected << NL;
		}
		else {
			std::cout << "PASSED TEST!" << NL;
		}
		std::cout << NL;
	}

	void RunTests() 
	{
		Define t0 = Begin() > int_(1) > int_(2) > add_ > ret_;
		Define t1 = Begin() > int_(42) > ret_;
		Define t2 = Begin() > t1 > call_ > ret_;
		Define t3 = Begin() > t1 > tail_;
		Define t4 = Begin() > t1 > call_ > t1 > call_ > add_ > ret_;
		Define t5 = Begin() > int_(13) > dup_ > add_ > ret_; 
		Define t6 = Begin() > int_(5) > int_(13) > sub_ > ret_; 
		Define t7 = Begin() > int_(13) > int_(5) > sub_ > ret_; 
		
		RunTest(t0, 3);
		RunTest(t1, 42);
		RunTest(t2, 42);
		RunTest(t3, 42);
		RunTest(t4, 84);
		RunTest(t5, 26);
		RunTest(t6, 8);
		RunTest(t7, -8);
	}
}
